package ly.count.ohos.sdk;

import ohos.aafwk.ability.Ability;
import ohos.bundle.AbilityInfo;
import ohos.global.configuration.Configuration;

import java.util.HashMap;
import java.util.Map;

public class ModuleViews extends ModuleBase {
    static final String VIEW_EVENT_KEY = "[CLY]_view";
    static final String ORIENTATION_EVENT_KEY = "[CLY]_orientation";
    private static final int CURRENT_ORIENTATION = 1;

    // track orientation changes
    boolean isTrackOrientationChanges = false;

    int currentOrientation = CURRENT_ORIENTATION;
    Class[] autoTrackingActivityExceptions = null; // excluded activities from automatic view tracking

    Map<String, Object> automaticViewSegmentation = new HashMap<>(); // automatic view segmentation
    // interface for SDK users
    final Views viewsInterface;
    ModuleLog moduleLog;

    private String lastView = null;
    private int lastViewStart = 0;
    private boolean isFirstView = true;

    ModuleViews(Countly cly, CountlyConfig config) {
        super(cly);

        moduleLog = cly.L;

        moduleLog.v("[ModuleViews] Initialising");

        mCly.setViewTracking(config.enableViewTracking);
        mCly.setAutoTrackingUseShortName(config.autoTrackingUseShortName);

        setAutomaticViewSegmentationInternal(config.automaticViewSegmentation);
        autoTrackingActivityExceptions = config.autoTrackingExceptions;
        isTrackOrientationChanges = config.trackOrientationChange;

        viewsInterface = new Views();
    }

    void setAutomaticViewSegmentationInternal(Map<String, Object> segmentation) {
        moduleLog.d("[ModuleViews] Calling setAutomaticViewSegmentationInternal");

        automaticViewSegmentation.clear();

        if (segmentation != null) {
            if (Utils.removeUnsupportedDataTypes(segmentation)) {
                // found a unsupported type, print warning

                moduleLog.w("[ModuleViews] You have provided a unsupported type for automatic View Segmentation");
            }

            Utils.removeKeysFromMap(segmentation, ModuleEvents.reservedSegmentationKeys);

            automaticViewSegmentation.putAll(segmentation);
        }
    }

    /**
     * Reports duration of last view
     */
    void reportViewDuration() {
        moduleLog.d("[ModuleViews] View [" + lastView
                + "] is getting closed, reporting duration: ["
                + (UtilsTime.currentTimestampSeconds() - lastViewStart)
                + "], current timestamp: ["
                + UtilsTime.currentTimestampSeconds()
                + "], last views start: [" + lastViewStart + "]");

        if (lastView != null && lastViewStart <= 0) {
            moduleLog.e("[ModuleViews] Last view start value is not normal: [" + lastViewStart + "]");
        }

        if (!mCly.getConsent(Countly.CountlyFeatureNames.views)) {
            return;
        }

        // only record view if the view name is not null and if it has a reasonable duration
        // if the lastViewStart is equal to 0, the duration would be set to the current timestamp
        // and therefore will be ignored
        if (lastView != null && lastViewStart > 0) {
            HashMap<String, Object> segments = new HashMap<>();

            segments.put("name", lastView);
            segments.put("dur", String.valueOf(UtilsTime.currentTimestampSeconds() - lastViewStart));
            segments.put("segment", "ohos");
            mCly.moduleEvents.recordEventInternal(VIEW_EVENT_KEY, segments, 1, 0, 0, null, true);
            lastView = null;
            lastViewStart = 0;
        }
    }

    boolean isAbilityInExceptionList(Ability act) {
        if (autoTrackingActivityExceptions == null) {
            return false;
        }

        for (Class autoTrackingAbilityException : autoTrackingActivityExceptions) {
            if (act.getClass().equals(autoTrackingAbilityException)) {
                return true;
            }
        }

        return false;
    }

    /**
     * Record a view manually, without automatic tracking
     * or track view that is not automatically tracked
     * like fragment, Message box or transparent Activity
     *
     * @param viewName String - name of the view
     * @param customViewSegmentation Map<String, Object> - segmentation that will be added to the view,
     * set 'null' if none should be added
     *
     * @return Returns link to Countly for call chaining
     */
    synchronized Countly recordViewInternal(String viewName, Map<String, Object> customViewSegmentation) {
        if (!mCly.isInitialized()) {
            moduleLog.e("Countly.sharedInstance().init must be called before recordView");
            return mCly;
        }

        if (viewName == null || viewName.isEmpty()) {
            moduleLog.e("[ModuleViews] Trying to record view with null or empty view name, ignoring request");
            return mCly;
        }

        if (moduleLog.logEnabled()) {
            int segmCount = 0;
            if (customViewSegmentation != null) {
                segmCount = customViewSegmentation.size();
            }
            moduleLog.d("[ModuleViews] Recording view with name: ["
                    + viewName + "], previous view:[" + lastView
                    + "] custom view segment count:[" + segmCount + "]");
        }

        reportViewDuration();
        lastView = viewName;
        lastViewStart = UtilsTime.currentTimestampSeconds();

        Map<String, Object> viewSegmentation = new HashMap<>();
        if (customViewSegmentation != null) {
            Utils.removeUnsupportedDataTypes(customViewSegmentation);
            Utils.removeKeysFromMap(customViewSegmentation, ModuleEvents.reservedSegmentationKeys);
            viewSegmentation.putAll(customViewSegmentation);
        }

        viewSegmentation.put("name", viewName);
        viewSegmentation.put("visit", "1");
        viewSegmentation.put("segment", "ohos");
        if (isFirstView) {
            isFirstView = false;
            viewSegmentation.put("start", "1");
        }

        mCly.moduleEvents.recordEventInternal(VIEW_EVENT_KEY, viewSegmentation, 1, 0, 0, null, true);

        return mCly;
    }

    void updateOrientation(int newOrientation) {
        moduleLog.d("[ModuleViews] Calling [updateOrientation], new orientation:[" + newOrientation + "]");

        if (!mCly.getConsent(Countly.CountlyFeatureNames.events)) {
            // we don't have consent, just leave
            return;
        }

        if (currentOrientation != newOrientation) {
            currentOrientation = newOrientation;

            Map<String, String> segm = new HashMap<>();

            if (currentOrientation == Configuration.DIRECTION_VERTICAL) {
                segm.put("mode", "portrait");
            } else {
                segm.put("mode", "landscape");
            }

            mCly.recordEvent(ORIENTATION_EVENT_KEY, segm, 1);
        }
    }

    @Override
    void onConfigurationChanged(Configuration newConfig) {
        if (isTrackOrientationChanges) {
            if (newConfig != null) {
                updateOrientation(newConfig.direction);
            }
        }
    }

    @Override
    void onAbilityStopped() {
        reportViewDuration();
    }

    @Override
    void onAbilityStarted(Ability ability) {
        if (mCly.autoViewTracker) {
            if (!isAbilityInExceptionList(ability)) {
                String usedAbilityName = "NULL Ability";

                if (ability != null) {
                    if (mCly.automaticTrackingShouldUseShortName) {
                        usedAbilityName = ability.getClass().getSimpleName();
                    } else {
                        usedAbilityName = ability.getClass().getName();
                    }
                }

                mCly.recordView(usedAbilityName, automaticViewSegmentation);
            } else {
                moduleLog.d("[ModuleViews] [onStart] Ignoring ability because it's in the exception list");
            }
        }

        // orientation tracking
        if (isTrackOrientationChanges) {
            AbilityInfo.DisplayOrientation orient = getOrientationFromAbility(ability);
            if (orient != null) {
                updateOrientation(orient.ordinal());
            }
        }
    }

    /**
     * Needed for mocking test result
     *
     * @param abilityInfo
     * @return AbilityInfo.DisplayOrientation
     */
    AbilityInfo.DisplayOrientation getOrientationFromAbilityInfo(AbilityInfo abilityInfo) {
        if (abilityInfo == null) {
            return null;
        }

        return abilityInfo.getOrientation();
    }

    /**
     * Needed for mocking test result
     *
     * @param ability
     * @return AbilityInfo.DisplayOrientation
     */
    AbilityInfo.DisplayOrientation getOrientationFromAbility(Ability ability) {
        if (ability == null) {
            return null;
        }
        AbilityInfo abilityInfo = ability.getAbilityInfo();
        if (abilityInfo != null) {
            return abilityInfo.getOrientation();
        } else {
            return null;
        }
    }

    @Override
    void halt() {
        if (automaticViewSegmentation != null) {
            automaticViewSegmentation.clear();
            automaticViewSegmentation = null;
        }
        autoTrackingActivityExceptions = null;
    }

    public class Views {
        /**
         * Check state of automatic view tracking
         *
         * @return boolean - true if enabled, false if disabled
         */
        public boolean isAutomaticViewTrackingEnabled() {
            synchronized (mCly) {
                moduleLog.i("[Views] Calling isAutomaticViewTrackingEnabled");

                return mCly.autoViewTracker;
            }
        }

        /**
         * Record a view manually, without automatic tracking
         * or track view that is not automatically tracked
         * like fragment, Message box or transparent Activity
         *
         * @param viewName String - name of the view
         * @return Returns link to Countly for call chaining
         */
        public Countly recordView(String viewName) {
            synchronized (mCly) {
                return recordView(viewName, null);
            }
        }

        /**
         * Record a view manually, without automatic tracking
         * or track view that is not automatically tracked
         * like fragment, Message box or transparent Activity
         *
         * @param viewName String - name of the view
         * @param viewSegmentation Map<String, Object> - segmentation that will be added to the view,
         * set 'null' if none should be added
         *
         * @return Returns link to Countly for call chaining
         */
        public Countly recordView(String viewName, Map<String, Object> viewSegmentation) {
            synchronized (mCly) {
                if (!mCly.isInitialized()) {
                    moduleLog.e("Countly.sharedInstance().init must be called before recordView");
                    return mCly;
                }

                moduleLog.i("[Views] Calling recordView [" + viewName + "]");

                return recordViewInternal(viewName, viewSegmentation);
            }
        }
    }
}
